package com.pig4cloud.pigx.contract.executer.order;

import com.pig4cloud.pigx.common.customize.exception.MqAckException;
import com.pig4cloud.pigx.common.customize.i18n.util.MessageUtils;
import com.pig4cloud.pigx.common.customize.mysql.utils.MysqlUtil;
import com.pig4cloud.pigx.common.customize.security.utils.SecurityUtil;
import com.pig4cloud.pigx.common.seata.utils.SeataUtils;
import com.pig4cloud.pigx.common.security.service.PigxUser;
import com.pig4cloud.pigx.contract.constant.ContractMqConstants;
import com.pig4cloud.pigx.contract.constant.I18nMessages;
import com.pig4cloud.pigx.contract.dto.ContractOrderCancelDTO;
import com.pig4cloud.pigx.contract.dto.ContractOrderCloseByPositionDTO;
import com.pig4cloud.pigx.contract.dto.ContractOrderCloseDTO;
import com.pig4cloud.pigx.contract.dto.ContractOrderNewDTO;
import com.pig4cloud.pigx.contract.entity.ContractOrder;
import com.pig4cloud.pigx.contract.entity.ContractPosition;
import com.pig4cloud.pigx.contract.enums.ContractOrderEnum;
import com.pig4cloud.pigx.contract.executer.order.canceler.OrderCanceler;
import com.pig4cloud.pigx.contract.executer.order.initializer.OrderInitializerManager;
import com.pig4cloud.pigx.contract.executer.order.matcher.OrderMatcherManager;
import com.pig4cloud.pigx.contract.executer.order.migrator.OrderMigrator;
import com.pig4cloud.pigx.contract.mapstruct.ContractOrderStruct;
import com.pig4cloud.pigx.contract.mongo.entity.ContractOrderMongo;
import com.pig4cloud.pigx.contract.mongo.entity.ContractSymbolMongo;
import com.pig4cloud.pigx.contract.utils.ContractUtils;
import com.pig4cloud.pigx.contract.vo.ContractOrderInitResVO;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 订单执行器
 *
 * @since 2022/8/1p 10:54
 */
@Slf4j
@Component
@AllArgsConstructor
public class OrderExecutor {

    private final ContractUtils contractUtils;
    private final OrderInitializerManager orderInitializerManager;
    private final OrderMatcherManager orderMatcherManager;
    private final OrderCanceler orderCanceler;
    private final OrderMigrator orderMigrator;

    private final MysqlUtil mysqlUtil;

    /**
     * 创建订单
     *
     * @param dto dto
     * @return {@link ContractOrderInitResVO}
     * @since 2022/8/17 14:47
     */
    public ContractOrderInitResVO init(ContractOrderNewDTO dto) {
        log.info("{} 开始创建订单", this.getClass().getSimpleName());
        return orderInitializerManager.init(dto);
    }

    /**
     * 监听 contract.order.new 队列，开启新增订单的撮合
     *
     * @param msg {id: 123, isBuy: "0"}
     * @since 2022/8/17 21:00
     */
    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue(value = ContractMqConstants.QUEUE_FANOUT_CONTRACT_ORDER_NEW),
                    exchange = @Exchange(value = ContractMqConstants.EXCHANGE_FANOUT_CONTRACT_ORDER_NEW, type = ExchangeTypes.FANOUT)
            )})
    public void match(ContractOrderMongo msg) {
        log.info("{} 开始撮合订单。msg：{}", this.getClass().getSimpleName(), msg);

        orderMatcherManager.match(msg);
    }

    /**
     * 监听 contract.order.new.robot 队列，开启新增机器人订单的撮合
     *
     * @param msg {id: 123, isBuy: "0"}
     * @since 2022/8/17 21:00
     */
    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue(value = ContractMqConstants.QUEUE_FANOUT_CONTRACT_ORDER_NEW_ROBOT_K_LINE),
                    exchange = @Exchange(value = ContractMqConstants.EXCHANGE_FANOUT_CONTRACT_ORDER_NEW_ROBOT_K_LINE, type = ExchangeTypes.FANOUT)
            )})
    public void matchRobot(ContractOrderMongo msg) {
        log.info("{} 开始撮合机器人订单。msg：{}", this.getClass().getSimpleName(), msg);

        orderMatcherManager.match(msg);
    }

    /**
     * 监听 contract.order.cancel 队列，处理订单撤销
     *
     * @param dto dto
     * @since 2022/8/17 21:00
     */
    @RabbitListener(queues = ContractMqConstants.QUEUE_FANOUT_CONTRACT_ORDER_CANCEL)
    public void cancelByMq(ContractOrderCancelDTO dto) {
        log.info("{} 开始撤销订单。参数：{}", this.getClass().getSimpleName(), dto);

        try {
            orderCanceler.cancel(dto);
        } catch (MqAckException ignore) {
        }
    }

    /**
     * 用户撤销单个订单
     *
     * @param dto dto
     * @since 2022/8/17 21:00
     */
    public void cancel(ContractOrderCancelDTO dto) {
        orderCanceler.cancel(dto);
    }

    /**
     * 撤销用户全部订单
     *
     * @param userId 用户id
     * @since 2022/8/17 21:00
     */
    public void cancelByUser(Long userId) {
        log.info("{} 开始根据用户撤销订单。id：{}", this.getClass().getSimpleName(), userId);
        orderCanceler.cancelByUser(userId);
    }

    /**
     * 监听 contract.order.expire 队列，处理过期订单
     *
     * @param dto dto
     * @since 2022/8/17 21:00
     */
    @RabbitListener(queues = ContractMqConstants.QUEUE_FANOUT_CONTRACT_ORDER_EXPIRE)
    public void expire(ContractOrderCancelDTO dto) {
        log.info("{} 开始撤销订单。参数：{}", this.getClass().getSimpleName(), dto);

        try {
            orderCanceler.expire(dto);
        } catch (MqAckException ignore) {
        }
    }

    /**
     * 平仓
     *
     * @param dto dto
     * @return {@link ContractOrderInitResVO}
     * @since 2022/9/6 14:38
     */
    @GlobalTransactional(rollbackFor = Throwable.class)
    public ContractOrderInitResVO close(ContractOrderCloseDTO dto) {
        // 判断入参
        if (Objects.isNull(dto.getPositionId())
                && (Strings.isBlank(dto.getSymbolName())
                || Strings.isBlank(dto.getPositionMode())
                || Strings.isBlank(dto.getSide()))) {
            throw new RuntimeException(MessageUtils.get(I18nMessages.DATA_IS_EMPTY));
        }

        // 查询仓位信息并锁定
        PigxUser user = SecurityUtil.getLoginUserNotNull();
        ContractPosition condition = new ContractPosition();
        condition.setId(dto.getPositionId());
        // 确保是本人的仓位
        condition.setUserId(user.getId());
        condition.setSymbolName(dto.getSymbolName());
        condition.setMode(dto.getPositionMode());
        condition.setSide(dto.getSide());
        ContractPosition position = contractUtils.getOneCurrentPosition(condition, true);
        dto.setPositionId(position.getId());

        // 检查仓位信息
        ContractUtils.checkCurrentPosition(position, false);
        if (dto.getNum().compareTo(position.getAvailableNum()) > 0) {
            throw new RuntimeException(MessageUtils.get(I18nMessages.INSUFFICIENT_NUMBER_OF_POSITIONS_CAN_BE_CLOSED));
        }

        // 将仓位信息设置到订单上
        ContractOrderNewDTO newDTO = ContractOrderStruct.INSTANCE.closeDTO2NewDTO(dto);
        ContractUtils.initOrderByPositionWhenClose(newDTO, position);
        // 生成平仓订单
        return init(newDTO);
    }

    /**
     * 监听 contract.position.close.all 队列，处理平仓操作
     *
     * @param dto dto
     * @since 2022/8/17 21:00
     */
    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue(value = ContractMqConstants.QUEUE_FANOUT_CONTRACT_POSITION_CLOSE_ALL),
                    exchange = @Exchange(value = ContractMqConstants.EXCHANGE_FANOUT_CONTRACT_POSITION_CLOSE_ALL, type = ExchangeTypes.FANOUT)
            )})
    public void closeByPositionMq(ContractOrderCloseByPositionDTO dto) {
        try {
            closeByPosition(dto);
        } catch (Throwable ignore) {
        }
    }

    /**
     * 市价全平
     *
     * @param dto dto
     * @return {@link ContractOrderInitResVO}
     * @since 2023/2/28 17:03
     */
    public ContractOrderInitResVO closeByPosition(ContractOrderCloseByPositionDTO dto) {
        log.info("{} 开始根据仓位平仓。id：{}", this.getClass().getSimpleName(), dto.getPositionId());

        if (Objects.isNull(dto.getPositionId())) {
            throw new RuntimeException("数据异常，positionId为空");
        }

        try {
            mysqlUtil.startLocalTx();
            SeataUtils.startGlobalTx();

            // 查询仓位并锁定
            ContractPosition condition = new ContractPosition();
            condition.setId(dto.getPositionId());
            ContractPosition position = contractUtils.getOneAvailablePosition(condition, true);

            // 查询可撤销的订单
            ContractOrder orderCondition = new ContractOrder();
            orderCondition.setUserId(position.getUserId());
            orderCondition.setSymbolId(position.getSymbolId());
            orderCondition.setSide(position.getSide());
            List<ContractOrder> orderList = contractUtils.getAllAvailableOrder(orderCondition, true);

            // 撤销订单，归还剩余金额，暂不清除缓存、发mq
            boolean hasRestAmount = orderCanceler.cancelBatchWhenLiquidate(orderList);

            // 查询币对信息
            ContractSymbolMongo symbol = contractUtils.getSymbolById(position.getSymbolId());
            ContractUtils.checkSymbol(symbol);

            ContractOrderNewDTO newDTO = new ContractOrderNewDTO();
            newDTO.setClientOrderId(dto.getClientOrderId());
            newDTO.setPositionId(position.getId());
            newDTO.setType(ContractOrderEnum.Type.MARKET.getValue());
            newDTO.setNum(position.getAvailableNum());

            // 将仓位信息设置到订单上
            ContractUtils.initOrderByPositionWhenClose(newDTO, position);
            // 生成平仓订单
            ContractOrderInitResVO res = init(newDTO);

            // 删除撤销订单的缓存
            if (!CollectionUtils.isEmpty(orderList)) {
                orderList.forEach(orderCanceler::cancelOrderCache);
            }

            // 发送历史订单迁移消息
            if (!CollectionUtils.isEmpty(orderList)) {
                List<Long> canceledOrderIdList = orderList.stream()
                        .map(ContractOrder::getId)
                        .collect(Collectors.toList());
                contractUtils.sendOrderMigrate(canceledOrderIdList);
            }

            // 如果订单有剩余的冻结金额
            if (hasRestAmount) {
                // 发送重算全仓强平信息
                contractUtils.sendPositionCrossCalculate(position.getUserId());
            }

            return res;

        } catch (Throwable e) {
            SeataUtils.rollbackGlobalTx();
            mysqlUtil.rollbackLocalTx();
            throw e;
        }
    }

    /**
     * 监听 contract.order.migrate 队列，历史订单迁移
     *
     * @param orderIdList 订单idList
     * @since 2022/9/20 16:20
     */
    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue(value = ContractMqConstants.QUEUE_FANOUT_CONTRACT_ORDER_MIGRATE),
                    exchange = @Exchange(value = ContractMqConstants.EXCHANGE_FANOUT_CONTRACT_ORDER_MIGRATE, type = ExchangeTypes.FANOUT)
            )})
    public void migrate(List<Long> orderIdList) {
        // 开启本地事务
        mysqlUtil.startLocalTx();
        try {
            // 迁移
            orderMigrator.migrator(orderIdList);
            // 提交本地事务
            mysqlUtil.commitLocalTx();
        } catch (Throwable e) {
            // 回滚本地事务
            mysqlUtil.rollbackLocalTx();

            if (e instanceof MqAckException) {
                return;
            }
            throw e;
        }
    }
}
